package redis

import (
	"context"
	"encoding/json"
	"fmt"
	"gitee.com/Cauchy_AQ/vxopen/model"
	"gitee.com/Cauchy_AQ/vxopen/utils"
	"github.com/redis/go-redis/v9"
	"sync"
	"time"
)

type (
	WechatAccessTokenReq struct {
		AppId     string `json:"app_id"`
		AppSecret string `json:"app_secret"`
		GrantType string `json:"grant_type"`
	}
	WechatAccessTokenResp struct {
		AccessToken string `json:"access_token"`
		ExpiresIn   int64  `json:"expires_in"`
	}
)

const (
	// API_VX_TOKEN 获取 access_token 的 url
	API_VX_TOKEN = "https://api.weixin.qq.com/cgi-bin/token"
)

const (
	// REFRESH_ATOKEN_TIME 刷新 aToken 的固定时间
	REFRESH_ATOKEN_TIME = time.Hour
	// CACHE_ATOKEN_TIME 缓存 aToken 的固定时间
	CACHE_ATOKEN_TIME = time.Minute
)

var (
	rdb          *redis.Client
	once         sync.Once
	isRefreshing bool
)

func GetRedisInstance(conf model.ModelConfig) (*redis.Client, error) {
	var err error
	once.Do(func() {
		rdb, err = newRedisDB(conf)
		go startRefreshAToken(conf)
	})
	return rdb, err
}

func newRedisDB(c model.ModelConfig) (*redis.Client, error) {
	client := redis.NewClient(&redis.Options{
		Addr:         fmt.Sprintf("%s:%d", c.RedisConfig.Host, c.RedisConfig.Port),
		Password:     c.RedisConfig.Password,
		DB:           c.RedisConfig.DB,
		PoolSize:     c.RedisConfig.PoolSize,
		MinIdleConns: c.RedisConfig.MinIdleConns,
	})
	_, err := client.Ping(context.Background()).Result()
	return client, err
}

// GetAvailableAToken 获取可用的 access_token
func GetAvailableAToken(conf model.ModelConfig) (string, error) {
	// 查询 redis 是否存在有效的 access_token
	aToken, err := rdb.Get(context.Background(), CacheATokenPrefix).Result()
	if err != nil && err != redis.Nil {
		return "", err
	}
	if err == redis.Nil {
		// 不存在或已过期
		err := refreshAToken(conf)
		if err != nil {
			return "", err
		}
		return rdb.Get(context.Background(), CacheATokenPrefix).Result()
	}
	return aToken, err
}

func refreshAToken(config model.ModelConfig) error {
	if isRefreshing {
		return nil
	}
	isRefreshing = true
	defer func() { isRefreshing = false }()

	resp1, err := getAccessToken(&WechatAccessTokenReq{
		AppId:     config.WeChatConfig.AppId,
		AppSecret: config.WeChatConfig.AppSecret,
		GrantType: config.WeChatConfig.GrantType,
	})
	if err != nil {
		return err
	}
	err = rdb.Set(context.Background(), CacheATokenPrefix, resp1.AccessToken, CACHE_ATOKEN_TIME).Err()
	if err != nil {
		return err
	}
	return nil
}

func startRefreshAToken(conf model.ModelConfig) {
	ticker := time.NewTicker(REFRESH_ATOKEN_TIME)
	for {
		select {
		case <-ticker.C:
			err := refreshAToken(conf)
			if err != nil {
				fmt.Println("[error] refresh access_token error")
			}
		}
	}
}

// https://api.weixin.qq.com/cgi-bin/token?grant_type=xxx&appid=xxx&secret=xxx
// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html
func getAccessToken(req *WechatAccessTokenReq) (*WechatAccessTokenResp, error) {
	urls := fmt.Sprintf("%s?grant_type=%s&appid=%s&secret=%s",
		API_VX_TOKEN, req.GrantType, req.AppId, req.AppSecret)
	body, err := utils.GetHttpBody(urls)
	if err != nil {
		return nil, err
	}
	w := &WechatAccessTokenResp{}
	err = json.Unmarshal(body, w)
	if err != nil {
		return nil, err
	}
	return w, nil
}
